
/**
 * nobitterwindow.cpp
 * 
 * NobitterWindow is the main window of nobitter
 * 
 * Copyright (c) 2008 Harunobu Oyama <nobukichi at gmail.com>
 * Licensed under GNU GPL version 3
 *
 */

#include	<QCoreApplication>
#include	<QDesktopServices>
#include	<QUrl>
#include	<QCloseEvent>
#include	<QCompleter>
#include	<QTimer>
#include	<qDebug>

#include	"usersettings.h"
#include	"nobitterwindow.h"
#include	"imagecache.h"
#include	"settingsdialog.h"
#include	"nobittercontrol.h"
#include	"twitterapi.h"
#include	"nobittermodel.h"

#ifdef Q_WS_WIN
#pragma comment(lib, "user32.lib")
#endif 

#ifdef Q_WS_MAC
#include	<CarbonEvents.h>
extern void qt_mac_set_dock_menu(QMenu *);

const OSType NOBITTER_APP_SIGNATURE = 765;
const UInt32 NOBITTER_HOTKEY_ID = 766;

static NobitterWindow* theMainWindow = 0;
static EventHotKeyRef g_HotKeyRef;

static pascal OSStatus MyMacHotKeyHandler(EventHandlerCallRef myHandler, EventRef event, void* userData)
{
#pragma unused(myHandler, userData)
	
    if ((GetEventClass(event) == kEventClassKeyboard) && (GetEventKind(event) == kEventHotKeyPressed)) {
		
		EventHotKeyID    hkey;
		GetEventParameter(event,kEventParamDirectObject, typeEventHotKeyID, 0, sizeof(EventHotKeyID), 0 , &hkey);
		
		if ((hkey.signature == NOBITTER_APP_SIGNATURE) && (hkey.id == NOBITTER_HOTKEY_ID)) {
			if (theMainWindow) {
				theMainWindow->toggleVisibility();
			}
			return noErr;
		}
    }
	
    return( eventNotHandledErr );
}

void InstallMacHotKey()
{
    EventHotKeyID    hkey = { NOBITTER_APP_SIGNATURE, NOBITTER_HOTKEY_ID };
    OSStatus status = RegisterEventHotKey((UInt32) 45, cmdKey+shiftKey, hkey, GetApplicationEventTarget(), 0, &g_HotKeyRef);
	(void) status;

    EventTypeSpec eventTypeSpec = { kEventClassKeyboard, kEventHotKeyPressed };
    EventHandlerRef eref;
    InstallApplicationEventHandler(NewEventHandlerUPP (MyMacHotKeyHandler), 1, &eventTypeSpec, 0, &eref);
}

void UninstallMacHotKey()
{
	UnregisterEventHotKey(g_HotKeyRef);
}

#endif

NobitterWindow::NobitterWindow(QWidget* parent, Qt::WindowFlags flags)
  : QMainWindow(parent, flags)
{
	setupUi(this);
	progressBar->setMinimum(0);

	//! initialize all member variables
	friendCompleter = 0;
	pControl = new NobitterControl(this);
    actionMinimize = new QAction(tr("minimize"), this);
    actionRestore = new QAction(tr("restore"), this);
    trayIcon= new QSystemTrayIcon(this);
	latestTweet = Tweet(QDateTime::currentDateTime());
	isShowingNewTweet = false;
	pNewTweetTimer = new QTimer(this);

	//! setup GUI widgets
	setupTrayIcon();
	setupMainView();
	setupWhenFilter();
	setupDockMenu();
	setDefaultIcon();

	//! connect signals
	connectActionSignals();
	connectGuiSignals();
	connectControllerSignals();

	//! start new tweet checking timer
	setupNewTweetTimer();

#ifdef Q_WS_WIN
	nobitterHotKey = GlobalAddAtom(TEXT("NOBITTER_HOTKEY_ATOM"));
	RegisterHotKey(winId(), nobitterHotKey, MOD_CONTROL|MOD_SHIFT, 'N');
#endif

#ifdef Q_WS_MAC
	theMainWindow = this;
	InstallMacHotKey();
#endif

	progressBar->setMinimum(0);
	progressBar->setMaximum(0); 
	QTimer::singleShot(0, pControl, SLOT(slotStart()));
}

NobitterWindow::~NobitterWindow()
{
#ifdef Q_WS_WIN
	UnregisterHotKey(winId(), nobitterHotKey);
#endif

#ifdef Q_WS_MAC
	UninstallMacHotKey();
#endif
}

#ifdef Q_WS_WIN
bool NobitterWindow::winEvent(MSG* message, long* result)
{
	if (message->message == WM_HOTKEY) {
		if (LOWORD(message->wParam) == nobitterHotKey) {
			toggleVisibility();		
			return 0;
		}
	}
	return QWidget::winEvent(message, result);
}
#endif

void NobitterWindow::toggleVisibility()
{
	setVisible(!isVisible());
	if (isVisible()) {
		activateWindow();
		raise();
		lineEdit->setFocus();
	}
}		

void NobitterWindow::slotRestoreWindow()
{
	showNormal();
	activateWindow();
	raise();
}

void NobitterWindow::slotSendButtonClicked()
{
	if (friendCompleter != 0 && friendCompleter->popup() != 0 && friendCompleter->popup()->isVisible()) {
		//! ignore Enter key, when it is pressed to confirm the selection from the completion list
		return;	
	}

	if (!lineEdit->text().isEmpty()) {
		pControl->slotSendMyTweet(lineEdit->text());
		lineEdit->clear();
	}
}

void NobitterWindow::slotDisableInput()
{
	labelStatus->setText("Request sent to the server");
	progressBar->setMaximum(0);
	pushButtonUpdate->setDisabled(true);
	pushButtonSend->setDisabled(true);
}

void NobitterWindow::slotEnableInput()
{
	labelStatus->setText("");
	progressBar->setRange(0, 100);
	progressBar->setValue(0);
	progressBar->update(progressBar->rect());
	pushButtonUpdate->setEnabled(true);
	pushButtonSend->setEnabled(true);
	updateWhoFilter();
	updateAtFilter();
	updateCompleter();
}

void NobitterWindow::updateCompleter()
{
	QStringList friends = pControl->getFriendNames();
	delete friendCompleter;
	friendCompleter = new QCompleter(friends, this);
	friendCompleter->setCaseSensitivity(Qt::CaseInsensitive);
	lineEdit->setCompleter(friendCompleter);
}

void NobitterWindow::updateWhoFilter()
{
	QStringList oldLabels;
	for (int i = 0; i < comboBoxWhoFilter->count(); ++i) {
		oldLabels << comboBoxWhoFilter->itemText(i);
	}
	QStringList newLabels = pControl->getWhoFilterLabels();
	if (oldLabels != newLabels) {
		QString activeFilter = comboBoxWhoFilter->currentText();
		comboBoxWhoFilter->clear();
		int newIndex = 0;
		for (int i = 0; i < newLabels.size(); ++i) {
			comboBoxWhoFilter->addItem(newLabels[i]);
			if (newLabels[i] == activeFilter) {
				newIndex = i;
			}
		}
		comboBoxWhoFilter->setCurrentIndex(newIndex);
	}
}

void NobitterWindow::updateAtFilter()
{
	QStringList oldLabels;
	for (int i = 0; i < comboBoxAtFilter->count(); ++i) {
		oldLabels << comboBoxAtFilter->itemText(i);
	}
	QStringList newLabels = pControl->getAtFilterLabels();
	if (oldLabels != newLabels) {
		QString activeFilter = comboBoxAtFilter->currentText();
		comboBoxAtFilter->clear();
		int newIndex = 0;
		for (int i = 0; i < newLabels.size(); ++i) {
			comboBoxAtFilter->addItem(newLabels[i]);
			if (newLabels[i] == activeFilter) {
				newIndex = i;
			}
		}
		comboBoxAtFilter->setCurrentIndex(newIndex);
	}
}

void NobitterWindow::setupTrayIcon()
{
    QMenu* trayIconMenu = new QMenu(this);
    trayIconMenu->addAction(actionMinimize);
    trayIconMenu->addAction(actionRestore);
    trayIconMenu->addSeparator();
    trayIconMenu->addAction(actionExit);
    trayIcon->setContextMenu(trayIconMenu);
	trayIcon->setToolTip("nobitter");
	trayIcon->show();
}

void NobitterWindow::setupNewTweetTimer()
{
	pNewTweetTimer->setSingleShot(false);
	pNewTweetTimer->setInterval(5 * 1000);
	connect( pNewTweetTimer, SIGNAL(timeout()),
	         this, SLOT(slotUpdateNewTweetBalloon()) );
	
	pNewTweetTimer->start();
}

void NobitterWindow::setupMainView()
{
	mainView->setRootIsDecorated(false);
	mainView->setAlternatingRowColors(true);
	mainView->setWordWrap(true);
	mainView->setModel(pControl->model());
}

void NobitterWindow::setupWhenFilter()
{
	QStringList labels = pControl->getWhenFilterLabels();
	for (int i = 0; i < labels.size(); ++i) {
		comboBoxWhenFilter->addItem(labels[i]);
	}
}

void NobitterWindow::setupDockMenu()
{
#ifdef Q_WS_MAC
	QMenu* docMenu = new QMenu;
	docMenu->addAction(actionSettings);
	docMenu->addAction(actionMinimize);
	docMenu->addAction(actionRestore);
	qt_mac_set_dock_menu(docMenu);
#endif
}

void NobitterWindow::connectActionSignals()
{
	connect( actionSettings, SIGNAL(triggered(bool)), 
	         pControl, SLOT(slotShowSettingsDialog()) );
	connect( actionExit, SIGNAL(triggered(bool)),
	         pControl, SLOT(slotQuit()) );
	connect( actionShowNobitterProjectPage, SIGNAL(triggered(bool)),
	         this, SLOT(slotShowProjectPage()) );
    connect(actionMinimize, SIGNAL(triggered()), this, SLOT(hide()));
    connect(actionRestore, SIGNAL(triggered()), this, SLOT(slotRestoreWindow()));
	connect( actionWhoFilter, SIGNAL(triggered(bool)), 
		comboBoxWhoFilter, SLOT(setFocus()) );
	connect( actionAtFilter, SIGNAL(triggered(bool)), 
	         comboBoxAtFilter, SLOT(setFocus()) );
	connect( actionTimeFilter, SIGNAL(triggered(bool)), 
	         comboBoxWhenFilter, SLOT(setFocus()) );
	connect( actionHideOrShow, SIGNAL(triggered(bool)), 
	         this, SLOT(hide()) );
	connect( actionLineEdit, SIGNAL(triggered(bool)), 
	         lineEdit, SLOT(setFocus()) );
	connect( actionMessageList, SIGNAL(triggered(bool)), 
	         mainView, SLOT(setFocus()) );
	connect( actionUpdate, SIGNAL(triggered(bool)), 
	         pControl, SLOT(slotUpdateMyTimeline()) );
}

void NobitterWindow::connectGuiSignals()
{
	connect( pushButtonUpdate, SIGNAL(clicked()),
	         pControl, SLOT(slotUpdateMyTimeline()) );
	connect( pushButtonSend, SIGNAL(clicked()),
	         this, SLOT(slotSendButtonClicked()) );
	connect( lineEdit, SIGNAL(returnPressed()), 
	         this, SLOT(slotSendButtonClicked()) );
	connect( lineEdit, SIGNAL(textEdited(const QString&)),
	         this, SLOT(slotUpdateLineEditColor(const QString&)) );
	
	connect( comboBoxWhoFilter, SIGNAL(currentIndexChanged (int)),
	         pControl, SLOT(slotChangeWhoFilter(int)) );
	connect( comboBoxAtFilter, SIGNAL(currentIndexChanged (int)),
	         pControl, SLOT(slotChangeAtFilter(int)) );
	connect( comboBoxWhenFilter, SIGNAL(currentIndexChanged (int)),
	         pControl, SLOT(slotChangeWhenFilter(int)) );
	
	connect( mainView, SIGNAL(doubleClicked(const QModelIndex&)),
	         this, SLOT(slotOpenUrl(const QModelIndex&)) );
	
	connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(slotRestoreWindow()));
}

void NobitterWindow::connectControllerSignals()
{
	connect( pControl, SIGNAL(signalRequestStarted()), 
	         this, SLOT(slotDisableInput()) );
	connect( pControl, SIGNAL(signalRequestDone()), 
	         this, SLOT(slotEnableInput()) );
	connect( pControl, SIGNAL(signalStatusUpdated(QString)), 
	         labelStatus, SLOT(setText(QString)) );

	connect( pControl->model(), SIGNAL(signalModelUpdated()),
	         this, SLOT(slotAdjustColumnWidth()) );
}

void NobitterWindow::setDefaultIcon()
{
	QIcon icon(":/images/nobita.png");
	trayIcon->setIcon(icon);
	QApplication::setWindowIcon(icon);
}

void NobitterWindow::setUserIcon(const QString& user)
{
	QIcon icon = ImageCache::getInstance()->icon(user);
	trayIcon->setIcon(icon);
	QApplication::setWindowIcon(icon);
}

void NobitterWindow::setVisible(bool visible)
{
	setDefaultIcon();
    actionMinimize->setEnabled(visible);
    actionRestore->setEnabled(!visible);
    QWidget::setVisible(visible);
}

void NobitterWindow::closeEvent(QCloseEvent* event)
{
    hide();
    event->ignore();
}

void NobitterWindow::slotToggleVisibility(QSystemTrayIcon::ActivationReason reason)
{
	if (reason != QSystemTrayIcon::Context) {
		setVisible(!isVisible());
	}
}

void NobitterWindow::slotUpdateLineEditColor(const QString& newText)
{
	//! update the background color of line edit 
	//! RED, if the user is using group, but sending a plain update
	//! WHITE, otherwise

	QString gateway = UserSettings::get()->gatewayUserName();
	QString bgColor = "white";
	if (!gateway.isEmpty()) {
		if (newText.startsWith("@") && newText.contains(" ")) {
			QString atUser = newText.mid(1, newText.indexOf(" ")-1);
			if (gateway != atUser) {
				bgColor = "red";
			}
		}
	}

	QString styleSheet = QString("QLineEdit { background: %1; }").arg(bgColor);
	lineEdit->setStyleSheet(styleSheet);
}

void NobitterWindow::slotShowProjectPage()
{
	QDesktopServices::openUrl(QUrl("http://code.google.com/p/nobitter/"));
}

void NobitterWindow::slotOpenUrl(const QModelIndex& index)
{
	QUrl url = extractUrl(index);
	if (url.isValid()) {
		QDesktopServices::openUrl(url);
	}
}

QUrl NobitterWindow::extractUrl(const QModelIndex& index)
{
	QString text = index.data().toString();
	int urlHead = text.indexOf("http://");
	if (urlHead < 0) {
		urlHead = text.indexOf("https://");
	}
	if (urlHead < 0) {
		return QUrl();
	}
	int urlTail = text.indexOf(" ", urlHead);
	if (urlHead < urlTail) {
		return QUrl(text.mid(urlHead, urlTail-urlHead));
	} else {
		return QUrl(text.mid(urlHead));
	}
}

void NobitterWindow::slotUpdateNewTweetBalloon()
{
	QString userName = UserSettings::get()->twitterUserName();
	if (userName.isEmpty()) {
		//! the do-while loop will cause an infinite busy loop, 
		//! if userName is empty
		return;
	}

	bool hasNewTweet = false;

	Tweet newTweet;
	do {
		newTweet = pControl->model()->tweetNewerThan(latestTweet);
		if (!newTweet.userName().isEmpty()) {
			//! if there is a new tweet, update latestTweet
			latestTweet = newTweet;
			hasNewTweet = true;
		}
	} while (newTweet.userName() == UserSettings::get()->twitterUserName());
	//! skip my own tweet updates

	//! update the log file, if we have a new tweet
	//! we save the log file every time detecting a new tweet because
	//! nobitter does not save the log file, if the user restarts OS.
	//! there might be a better way, like handling WM_QUERRYENDSESSION,
	//! but I have no idea how to deal with the situation on Mac and Linux.
	if (hasNewTweet && UserSettings::get()->saveLog()) {
		pControl->slotSave();
	}

	if (!newTweet.userName().isEmpty()) {
		trayIcon->showMessage(newTweet.userName(), newTweet.statusText(), QSystemTrayIcon::Information, 5000);
		setUserIcon(newTweet.userName());
		isShowingNewTweet = true;
	} else if (isShowingNewTweet) {
		//! Need to hide the balloon text as Windows does not respects the specified timeout
		trayIcon->hide();
		trayIcon->show();
		isShowingNewTweet = false;
	}
}

void NobitterWindow::slotAdjustColumnWidth()
{
	mainView->resizeColumnToContents(0);
	mainView->resizeColumnToContents(1);
	mainView->resizeColumnToContents(2);
	mainView->resizeColumnToContents(3);
}

